package com.bblocks.common.anti_reptile.rateLimiter;

import com.bblocks.common.anti_reptile.constant.AntiReptileConsts;
import com.bblocks.common.anti_reptile.constant.RuleResultEnum;
import com.bblocks.common.anti_reptile.constant.RuleTypeEnum;
import com.bblocks.common.anti_reptile.service.TokenService;
import com.github.benmanes.caffeine.cache.Cache;
import com.github.benmanes.caffeine.cache.Caffeine;
import lombok.NonNull;
import lombok.extern.slf4j.Slf4j;
import org.redisson.api.RMapCache;
import org.redisson.api.RedissonClient;
import org.redisson.api.map.event.EntryEvent;
import org.redisson.api.map.event.EntryExpiredListener;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.context.annotation.Lazy;
import org.springframework.util.ConcurrentReferenceHashMap;

import javax.annotation.PostConstruct;
import javax.annotation.Resource;
import java.util.Arrays;
import java.util.HashMap;
import java.util.Map;
import java.util.Optional;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicInteger;

/**
 * <p>Project: bb-anti-reptile - BlackListService</p>
 * <p>描述：黑名单服务（支持二级缓存）
 * 查询是否黑名单存在本地存储（存贮全部的id是否属于黑名单），但是会存延时问题（目前默认方案）
 * </p>
 *
 * @Author Zhao [125043150@qq.com]
 * @Date 2024/4/14 17:51
 * @Version 1.0
 * @since 8
 */
@ConditionalOnMissingBean(BlackListService.class)
@Slf4j
public class BlackListServiceDefault implements BlackListService {

    /**
     * 本地缓存最大数量
     */
    @Value("${anti.reptile.rateLimiter.localCacheSizeMax:10000}")
    private Integer localCacheSizeMax;

    /**
     * 本地缓存有效期（时间越大性能越好，但是存在滞后性）
     * 设置5，意味着最大拉黑偏差存:5秒
     */
    @Value("${anti.reptile.rateLimiter.localCacheTime:5}")
    private Integer localCacheTime;

    @Lazy
    @Resource
    private RedissonClient redissonClient;
    /**
     * 全部的Map初始化
     */
    private Map<RuleTypeEnum, RMapCache<String,Integer>> rateLimiterMaps = new HashMap<>();

    /**
     * 存储是否黑名单《id，1,2,3/0》,value=RuleTypeEnum.TYPE,>0为黑名单
     */
    private Cache<String, Byte> idCache;


    @PostConstruct
    public void init(){
        Arrays.stream(RuleTypeEnum.values()).forEach(rt -> {
            RMapCache<String,Integer> rHitMap = redissonClient.getMapCache(AntiReptileConsts.BB_ANTI_PRE + rt.getType() + RateLimiterPlanRedis.RATELIMITER_HIT_CRAWLERSTRATEGY);
            rateLimiterMaps.put(rt, rHitMap);
        });

        idCache = Caffeine.newBuilder()
                .expireAfterWrite(localCacheTime, TimeUnit.SECONDS)
                .maximumSize(localCacheSizeMax)
                .build();
    }


    public RuleResultEnum containBlack(RuleTypeEnum ruleTypeEnum, String id) {
        int lockType = containBlackWithLockType(ruleTypeEnum, id);
        if( lockType >= 1 )
            return RuleResultEnum.getRuleResult(lockType);
        return null;
    }
    public int join(RuleTypeEnum ruleTypeEnum, String id, String requestUrl, long time, TimeUnit unit,@NonNull RuleResultEnum theRuleResultEnum) {
        Optional.ofNullable(rateLimiterMaps.get(ruleTypeEnum))
                .ifPresent(ruleMap -> ruleMap.put(id, theRuleResultEnum.getLockType(), time, unit));
        idCache.put(id,theRuleResultEnum.getLockType().byteValue());
        return 1;
    }

    public RuleResultEnum out(RuleTypeEnum ruleTypeEnum,String id, RuleResultEnum theRuleResultEnum) {
        int lockType = containBlackWithLockType(ruleTypeEnum,id);
        if(lockType == 0){
            return null;
        }
        if(theRuleResultEnum !=null &&  lockType != theRuleResultEnum.getLockType()){
            return null;
        }
        Optional.ofNullable(rateLimiterMaps.get(ruleTypeEnum))
                .ifPresent(ruleMap -> ruleMap.remove(id));
        idCache.put(id,(byte)0);
        return RuleResultEnum.getRuleResult(lockType);
    }

    /**
     * 是否包含黑名单
     * @param ruleTypeEnum
     * @param id
     * @return 0=否，1,2,3则为相关的锁定类型
     */
    private int containBlackWithLockType(RuleTypeEnum ruleTypeEnum, String id) {
        byte blackFlag = idCache.get(id,o->{
            Integer lockType = (Integer)rateLimiterMaps.get(ruleTypeEnum).get(id);
            if(lockType != null){
                return lockType.byteValue();
            }else {
                return (byte)0;
            }
        });
        return (int)blackFlag ;
    }

}
